// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition.Hosting
{
    public partial class CompositionContainer : ExportProvider, ICompositionService, IDisposable
    {
        private CompositionEngine _compositionEngine;
        private ComposablePartExportProvider _partExportProvider;
        private AggregateExportProvider _aggregatingExportProvider;
        private AdaptingExportProvider _rootProvider;
        private CatalogExportProvider _catalogExportProvider;
        private readonly ReadOnlyCollection<ExportProvider> _providers;
        private volatile bool _isDisposed = false;
        private Lock _lock = new Lock();
        private EventHandler<ExportsChangedEventArgs> _changedEventStorage;

        /// <summary>
        ///     Initializes a new instance of the <see cref="CompositionContainer"/> class.
        /// </summary>
        public CompositionContainer()
            : this((ComposablePartCatalog)null)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CompositionContainer"/> class 
        ///     with the specified export providers.
        /// </summary>
        /// <param name="providers">
        ///     A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide 
        ///     the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
        ///     or <see langword="null"/> to set <see cref="Providers"/> to an empty
        ///     <see cref="ReadOnlyCollection{T}"/>.
        /// </param>
        /// <exception cref="ArgumentException">
        ///     <paramref name="providers"/> contains an element that is <see langword="null"/>.
        /// </exception>
        public CompositionContainer(params ExportProvider[] providers) : 
            this((ComposablePartCatalog)null, providers)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CompositionContainer"/> class 
        ///     with the specified catalog and export providers.
        /// </summary>
        /// <param name="providers">
        ///     A <see cref="Array"/> of <see cref="ExportProvider"/> objects which provide 
        ///     the <see cref="CompositionContainer"/> access to <see cref="Export"/> objects,
        ///     or <see langword="null"/> to set <see cref="Providers"/> to an empty 
        ///     <see cref="ReadOnlyCollection{T}"/>.
        /// </param>
        /// <exception cref="ArgumentException">
        ///     <paramref name="providers"/> contains an element that is <see langword="null"/>.
        /// </exception>
        public CompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
        {
            Requires.NullOrNotNullElements(providers, "providers");

            this._compositionEngine = new CompositionEngine();
            this._compositionEngine.SourceProvider = this;
            this._partExportProvider = new ComposablePartExportProvider();
            this._partExportProvider.SourceProvider = this;
            this._providers = new ReadOnlyCollection<ExportProvider>((ExportProvider[])providers.Clone() ?? new ExportProvider[0]);

            if (catalog != null)
            {
                this._catalogExportProvider = new CatalogExportProvider(catalog);
                this._catalogExportProvider.SourceProvider = this;
            }

            this._rootProvider = new AdaptingExportProvider();
            this._rootProvider.ExportsChanged += this.OnExportsChangedInternal;

            providers = new ExportProvider[] { this._partExportProvider, this._catalogExportProvider }.Concat(this._providers).WhereNotNull().ToArray();

            // we only build the aggregating provider if necessary - that is, if we have more than one provider to aggregate
            if (providers.Length > 1)
            {
                this._aggregatingExportProvider = new AggregateExportProvider(providers);
                this._rootProvider.SourceProvider = this._aggregatingExportProvider;
            }
            else
            {
                Assumes.IsTrue(providers.Length == 1);
                this._rootProvider.SourceProvider = providers[0];
            }
        }

        /// <summary>
        ///     Gets the catalog which provides the container access to exports produced
        ///     from composable parts.
        /// </summary>
        /// <value>
        ///     The <see cref="ComposablePartCatalog"/> which provides the 
        ///     <see cref="CompositionContainer"/> access to exports produced from
        ///     <see cref="ComposablePart"/> objects. The default is <see langword="null"/>.
        /// </value>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        public ComposablePartCatalog Catalog
        {
            get 
            {
                ThrowIfDisposed();

                if (_catalogExportProvider != null)
                {
                    return _catalogExportProvider.Catalog;
                }

                return null;
            }
        }

        /// <summary>
        ///     Gets the export providers which provide the container access to additional exports.
        /// </summary>
        /// <value>
        ///     A <see cref="ReadOnlyCollection{T}"/> of <see cref="ExportProvider"/> objects
        ///     which provide the <see cref="CompositionContainer"/> access to additional
        ///     <see cref="Export"/> objects. The default is an empty 
        ///     <see cref="ReadOnlyCollection{T}"/>.
        /// </value>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        public ReadOnlyCollection<ExportProvider> Providers
        {
            get
            {
                this.ThrowIfDisposed();

                return this._providers;
            }
        }

        /// <summary>
        ///     Occurs when the exports in the <see cref="ExportProvider"/> change.
        /// </summary>
        /// <remarks>
        ///     The <see cref="ExportsChanged" /> event passes an <see cref="ExportsChangedEventArgs" />
        ///     which provides a list of contract names for the exports that have changed. An export
        ///     is considered to have changed if it has been added, removed, or updated in the 
        ///     <see cref="AggregateExportProvider" />.
        /// </remarks>
        public override event EventHandler<ExportsChangedEventArgs> ExportsChanged
        {
            add
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage += value;
                }
            }
            remove
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage -= value;
                }
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!this._isDisposed)
                {
                    bool disposeLock = false;
                    AdaptingExportProvider sourceProvider = null;
                    AggregateExportProvider aggregatingExportProvider = null;
                    ComposablePartExportProvider mutableExportProvider = null;
                    CatalogExportProvider catalogExportProvider = null;
                    CompositionEngine compositionEngine = null;
                    using (new WriteLock(this._lock))
                    {
                        if (!this._isDisposed)
                        {
                            sourceProvider = this._rootProvider;
                            aggregatingExportProvider = this._aggregatingExportProvider;
                            mutableExportProvider = this._partExportProvider;
                            catalogExportProvider = this._catalogExportProvider;
                            compositionEngine = this._compositionEngine;

                            this._rootProvider = null;
                            this._partExportProvider = null;
                            this._aggregatingExportProvider = null;
                            this._catalogExportProvider = null;
                            this._compositionEngine = null;

                            this._isDisposed = true;
                            disposeLock = true;
                        }
                    }

                    if (sourceProvider != null)
                    {
                        sourceProvider.ExportsChanged -= this.OnExportsChangedInternal;
                        sourceProvider.Dispose();
                    }

                    if (aggregatingExportProvider != null)
                    {
                        aggregatingExportProvider.Dispose();
                    }

                    if (mutableExportProvider != null)
                    {
                        mutableExportProvider.Dispose();
                    }

                    if (catalogExportProvider != null)
                    {
                        catalogExportProvider.Dispose();
                    }

                    if (compositionEngine != null)
                    {
                        compositionEngine.Dispose();
                    }

                    if (disposeLock)
                    {
                        this._lock.Dispose();
                    }
                }
            }
        }

  
        public void Compose(CompositionBatch batch)
        {
            Requires.NotNull(batch, "batch");

            this.ThrowIfDisposed();
            this._partExportProvider.Compose(batch);
        }

        /// <summary>
        ///     Releases the <see cref="Export"/> from the <see cref="CompositionContainer"/>. Depending 
        ///     on which <see cref="ExportProvider"/> this <see cref="Export"/> came from Release will cause
        ///     different behavior to occur.
        /// 
        ///     For example the <see cref="CatalogExportProvider"/> will only release 
        ///     an <see cref="Export"/> if it comes from a <see cref="ComposablePart"/> that was constructed
        ///     under a <see cref="CreationPolicy.NonShared" /> context. Release in this context means walking
        ///     the dependency chain of the <see cref="Export"/>s and removing references and calling Dispose on 
        ///     the <see cref="ComposablePart"/>s as needed. If the <see cref="Export"/> was constructed under
        ///     a <see cref="CreationPolicy.Shared" /> context the <see cref="CatalogExportProvider"/>
        ///     will do nothing because it is a shared <see cref="Export"/> and may be in use by other requestors. 
        /// </summary>
        /// <param name="export"><see cref="Export"/> that needs to be released.</param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="export"/> is <see langword="null"/>.
        /// </exception>
        [SuppressMessage("Microsoft.Performance", "CA1822")]
        public void ReleaseExport(Export export)
        {
            Requires.NotNull(export, "export");

            IDisposable dependency = export as IDisposable;

            if (dependency != null)
            {
                dependency.Dispose();
            }
        }

        /// <summary>
        ///     Satisfies the imports of the specified composable part and registering for it for 
        ///     recomposition if requested.
        /// </summary>
        /// <param name="part">
        ///     The <see cref="ComposablePart"/> to set the imports and register for recomposition.
        /// </param>
        /// <param name="registerForRecomposition">
        ///     Indicates whether the part will keep recomposing after the initial import satisfaction
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="CompositionException">
        ///     An error occurred during composition. <see cref="CompositionException.Errors"/> will
        ///     contain a collection of errors that occurred.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        public void SatisfyImports(ComposablePart part, bool registerForRecomposition)
        {
            this.ThrowIfDisposed();
            this._compositionEngine.SatisfyImports(part, registerForRecomposition);
        }

        /// <summary>
        ///     Unregisters the specified part from recomposition.
        /// </summary>
        /// <param name="part">
        ///     The <see cref="ComposablePart"/> to unregister from recomposition.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        public void UnregisterForRecomposition(ComposablePart part)
        {
            this.ThrowIfDisposed();
            this._compositionEngine.UnregisterForRecomposition(part);
        }

        internal void OnExportsChangedInternal(object sender, ExportsChangedEventArgs e)
        {
            this.OnExportsChanged(e);
        }

        /// <summary>
        ///     Raises the <see cref="ExportsChanged"/> event.
        /// </summary>
        /// <param name="e">
        ///     An <see cref="ExportsChangedEventArgs"/> containing the event data.
        /// </param>
        protected override void OnExportsChanged(ExportsChangedEventArgs e)
        {
            EventHandler<ExportsChangedEventArgs> changedEvent = null;

            using (new ReadLock(this._lock))
            {
                changedEvent = this._changedEventStorage;
            }

            if (changedEvent != null)
            {
                CompositionResult result = CompositionServices.TryFire(changedEvent, this, e);
                result.ThrowOnErrors();
            }
        }

        /// <summary>
        /// Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="definition">The <see cref="ImportDefinition"/> that defines the conditions of the
        /// <see cref="Export"/> to get.</param>
        /// <returns></returns>
        /// <result>
        /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
        /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
        /// empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        /// 	<note type="inheritinfo">
        /// The implementers should not treat the cardinality-related mismatches as errors, and are not
        /// expected to throw exceptions in those cases.
        /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
        /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
        /// </note>
        /// </remarks>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition)
        {
            this.ThrowIfDisposed();

            return this._rootProvider.GetExports(definition);
        }

        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (this._isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
    }
}
